import asyncio
import json
import logging
import os
import re
import sys
from datetime import datetime, timedelta

import aiohttp
import aioschedule as schedule
import dotenv
import regex
from aiogram import Bot, Dispatcher, F, types
from aiogram.filters import CommandStart
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup

dotenv.load_dotenv()

token = os.getenv('TOKEN')

bot = Bot(token)
dp = Dispatcher()

# Константы
API_URL = "http://localhost:8080/api"
MORNING_TIME = "5:00"
NIGHT_TIME = "22:00"
REMINDER_TYPES = {
    'Single': 'Единоразовое',
    'EveryYear': 'Ежегодное',
    'EveryMonth': 'Ежемесячное',
    'EveryWeek': 'Еженедельное',
    'EveryDay': 'Ежедневное'
}

class AddFood(StatesGroup):
    awaiting_weight = State()
    food = State()
    reminder_day = State()
    reminder_day_another = State()
    reminder_freq = State()
    reminder_name = State()
    reminder_curr = State()
    reminder_time = State()


def main_menu_keyb():
    kb = [
        [
            types.KeyboardButton(text="КБЖУ"),
            types.KeyboardButton(text="Напоминания")
        ],
    ]
    return types.ReplyKeyboardMarkup(
        keyboard=kb,
        resize_keyboard=True,
        input_field_placeholder="Выберите действие"
    )


def cancel_keyb():
    kb = [
        [
            types.KeyboardButton(text="Отменить", ),
        ]
    ]
    return types.ReplyKeyboardMarkup(
        keyboard=kb,
        resize_keyboard=True,
        input_field_placeholder="Выберите действие"
    )


def menu_reminder():
    kb = [
        [
            types.KeyboardButton(text="Текущие напоминания"),
        ],
        [
            types.KeyboardButton(text="Создать напоминание")
        ],
        [
            types.KeyboardButton(text="Отменить")
        ]
    ]
    return types.ReplyKeyboardMarkup(
        keyboard=kb,
        resize_keyboard=True,
        input_field_placeholder="Выберите действие"
    )


def menu_pfc():
    kb = [
        [
            types.KeyboardButton(text="Подсчитать КБЖУ")
        ],
        [
            types.KeyboardButton(text="Статистика")
        ],
        [
            types.KeyboardButton(text="Отменить")
        ]
    ]
    return types.ReplyKeyboardMarkup(
        keyboard=kb,
        resize_keyboard=True,
        input_field_placeholder="Выберите действие:"
    )


@dp.message(CommandStart())
async def start_message(message: types.Message, state: FSMContext):
    cur_state = await state.get_state()
    if cur_state is not None:
        await state.clear()
    async with aiohttp.ClientSession() as session:
        try:
            print(json.dumps({"telegramId": str(
                message.from_user.id), "username": message.from_user.username}))
            data = await session.post(API_URL+"/bot", data=json.dumps({"telegramId": str(message.from_user.id), "username": message.from_user.username}), headers={"Content-Type": "application/json"})
            print(data)
        except:
            await message.answer('Произошла ошибка, напишите /start', reply_markup=types.ReplyKeyboardRemove())
            return
    await message.answer('Приветствуем в нашем боте по отслеживанию КБЖУ\.', parse_mode='markdownv2', reply_markup=main_menu_keyb())


@dp.message(F.text.lower() == 'отменить')
async def start_message(message: types.Message, state: FSMContext):
    await state.clear()
    await message.answer("Действие отменено!", reply_markup=main_menu_keyb())


@dp.callback_query(F.data.casefold() == 'cancel')
async def start_message(callback: types.CallbackQuery, state: FSMContext):
    await state.clear()
    await callback.message.edit_text("Отменено")
    await callback.answer()


@dp.message(F.text.casefold() == 'кбжу')
async def pfc_menu_handle(message: types.Message):
    await message.answer("Выберите действие:", reply_markup=menu_pfc())


@dp.message(F.text.lower() == 'подсчитать кбжу')
async def add_food(message: types.Message, state: FSMContext):
    await state.set_state(AddFood.food)
    await message.answer('Введите текстом еду, что вы съели', reply_markup=cancel_keyb())


@dp.message(AddFood.food)
async def adding_food(message: types.Message, state: FSMContext):
    foods = ["Вы съели: "]
    async with aiohttp.ClientSession() as session:
        async with session.post(API_URL+"/pfc/add-food/"+str(message.from_user.id), data=json.dumps({"food": message.text}), headers={"Content-Type": "application/json"}) as resp:
            try:
                body = await resp.json()
                if len(body) > 1:
                    for food in body[:-1]:
                        foods.append(
                            f"Название: {food['name']}\nКалории: {food['calories']}ккал\nБЖУ: {food['protein']}/{food['totalFat']}/{food['carbohydrate']}г")
                    foods.append(
                        f"Всего: \nКалории: {body[-1]['calories']}ккал\nБЖУ: {body[-1]['protein']}/{body[-1]['totalFat']}/{body[-1]['carbohydrate']}г")
                else:
                    raise Exception
            except:
                foods = [
                    'Произошла ошибка. Измените ввод и попробуйте снова.']
    await message.answer('\n\n'.join(foods), reply_markup=main_menu_keyb())
    await state.clear()


@dp.message(F.text.lower() == 'статистика')
async def statistics(message: types.Message):
    async with aiohttp.ClientSession() as session:
        async with session.get(API_URL+"/pfc/get-today-count-calories/"+str(message.from_user.id)) as resp:
            try:
                json = await resp.json()
                current_date = datetime.now()
                date = current_date.strftime("%d.%m.%Y")
                await message.answer(f"Ваша статистика за день ({date}):\nКалории: {json['calories']}ккал\nБЖУ: {json['protein']}/{json['totalFat']}/{json['carbohydrate']}г")
            except:
                await message.answer('У вас нет статистики за этот день')


@dp.message(F.text.casefold() == 'напоминания')
async def reminder_menu_handle(message: types.Message):
    await message.answer('Выберите действие:', reply_markup=menu_reminder())


@dp.message(F.text.lower() == 'создать напоминание')
async def add_reminder_name(message: types.Message, state: FSMContext):
    await state.set_state(AddFood.reminder_name)
    await message.answer('Введите текст напоминания:', reply_markup=cancel_keyb())


@dp.message(AddFood.reminder_name)
async def add_reminder_day(message: types.Message, state: FSMContext):
    await state.update_data(reminder_name=message.text)
    await state.set_state(AddFood.reminder_day)
    kb = [
        [
            types.InlineKeyboardButton(
                text='Завтра', callback_data='data_tomorrow'),
            types.InlineKeyboardButton(
                text='Послезавтра', callback_data='data_aftertomorrow'),
        ],
        [
            types.InlineKeyboardButton(
                text='Другое', callback_data='data_another'),
        ]
    ]
    keyboard = types.InlineKeyboardMarkup(
        inline_keyboard=kb
    )
    await message.answer("Выберите день напоминания", reply_markup=keyboard)


@dp.callback_query(F.data.startswith("data_"))
async def add_reminder_freq(callback: types.CallbackQuery, state: FSMContext):
    cur_state = await state.get_state()
    if cur_state != AddFood.reminder_day:
        await callback.message.delete()
        await callback.answer()
        return
    await state.update_data(date=callback.data.split('data_')[1])
    if callback.data.split('data_')[1] == 'another':
        await callback.message.edit_text('Введите число месяца напоминания')
        await state.set_state(AddFood.reminder_day_another)
        return
    elif callback.data.split('data_')[1] == 'tomorrow':
        await state.update_data(day=0)
    elif callback.data.split('data_')[1] == 'aftertomorrow':
        await state.update_data(day=0)
    await state.set_state(AddFood.reminder_freq)
    kb = [
        [
            types.InlineKeyboardButton(
                text='Единожды', callback_data='freq_Single'),
        ],
        [
            types.InlineKeyboardButton(
                text="Ежедневно", callback_data='freq_EveryDay'
            )
        ],
        [
            types.InlineKeyboardButton(
                text='Еженедельно', callback_data='freq_EveryWeek'),
            types.InlineKeyboardButton(
                text='Ежемесячно', callback_data='freq_EveryMonth'),
            types.InlineKeyboardButton(
                text='Ежегодно', callback_data='freq_EveryYear'),
        ]
    ]
    keyboard = types.InlineKeyboardMarkup(
        inline_keyboard=kb
    )
    await callback.message.edit_text('Дата выбрана.')
    await bot.send_message(callback.message.chat.id, 'Выберите частоту повторения:', reply_markup=keyboard)
    await callback.answer()


@dp.message(AddFood.reminder_day_another)
async def add_reminder_day_another(message: types.Message, state: FSMContext):
    try:
        day = int(message.text)
        if (day < 1) or (day > 31):
            raise Exception()
        await state.update_data(day=day)
        await state.set_state(AddFood.reminder_freq)
        kb = [
            [
                types.InlineKeyboardButton(
                    text='Единожды', callback_data='freq_Single'),
            ],
            [
                types.InlineKeyboardButton(
                    text="Ежедневно", callback_data='freq_EveryDay'
                )
            ],
            [
                types.InlineKeyboardButton(
                    text='Еженедельно', callback_data='freq_EveryWeek'),
                types.InlineKeyboardButton(
                    text='Ежемесячно', callback_data='freq_EveryMonth'),
                types.InlineKeyboardButton(
                    text='Ежегодно', callback_data='freq_EveryYear'),
            ]
        ]
        keyboard = types.InlineKeyboardMarkup(
            inline_keyboard=kb
        )
        await message.answer('Дата выбрана.')
        await message.answer('Выберите частоту повторения:', reply_markup=keyboard)
    except:
        await message.answer("Введите корректную дату")


async def send_reminder(data: dict, from_user_id: str):
    req = dict()
    req['message'] = data['reminder_name']
    req['restartMode'] = data['freq']
    now_date = datetime.now()
    match data['date']:
        case 'tomorrow':
            now_date += timedelta(days=1)
        case 'aftertomorrow':
            now_date += timedelta(days=2)
    h, m = map(int, data['time'].split(':'))
    now_date = now_date.replace(hour=h, minute=m, second=0, microsecond=0)
    str_date = now_date.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
    req['dateNotif'] = str_date
    async with aiohttp.ClientSession() as session:
        await session.post(API_URL+'/notification/add-note/'+from_user_id, data=json.dumps(req), headers={'Content-Type': 'application/json'})


@dp.callback_query(F.data.startswith("freq_"))
async def add_reminder_freq(callback: types.CallbackQuery, state: FSMContext):
    print('freq trigger')
    cur_state = await state.get_state()
    if cur_state != AddFood.reminder_freq:
        await callback.message.delete()
        await callback.answer()
        return
    await state.update_data(freq=callback.data.split('freq_')[1])
    await callback.message.answer('Укажите время напоминаний в формате "12:00"')
    await callback.message.delete()
    await state.set_state(AddFood.reminder_time)
    await callback.answer()


@dp.message(AddFood.reminder_time)
async def add_reminder_finish(message: types.Message, state: FSMContext):
    if not regex.match(r'^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$', message.text):
        await message.answer('Попробуйте снова. Введите время в формате "12:00"')
        return
    await state.update_data(time=message.text)
    await send_reminder(await state.get_data(), str(message.chat.id))
    await state.clear()
    await message.answer('Напоминание успешно добавлено')
    await message.answer('Выберите действие.', reply_markup=main_menu_keyb())


async def get_reminders_req(from_id: str):
    print(from_id)
    async with aiohttp.ClientSession() as session:
        async with session.get(API_URL+"/notification/get-list-notes/"+from_id) as resp:
            return await resp.json()

@dp.message(F.text.casefold() == 'текущие напоминания')
async def cur_reminder(message: types.Message, state: FSMContext):
    await state.set_state(AddFood.reminder_curr)
    try:
        reminders = await get_reminders_req(str(message.from_user.id))
    except:
        await message.answer('Произошла ошибка', keyboard=menu_reminder())
        await state.clear()
        return
    await state.update_data(reminders=reminders)
    if len(reminders) == 0:
        await message.answer('Нет напоминаний', keyboard=menu_reminder())
        await state.clear()
        return
    kb = []
    if len(reminders) > 1:
        kb.append([
            types.InlineKeyboardButton(text="->", callback_data='curr_next'),
        ])
    kb.append(
        [
            types.InlineKeyboardButton(
                text="удалить", callback_data="delete_note")
        ]
    )
    keyboard = types.InlineKeyboardMarkup(
        inline_keyboard=kb
    )
    await state.update_data(curr_id=0)
    date = datetime.strptime(
        reminders[0]['dateNotif'], '%Y-%m-%dT%H:%M:%S.%fZ')
    sdate = datetime.strftime(date, "%d.%m.%Y %H:%M")
    
    await message.answer(f"Название: {reminders[0]['message']}\n{sdate}\nТип: {REMINDER_TYPES[reminders[0]['restartMode']]}", reply_markup=keyboard)


@dp.callback_query(F.data == 'curr_prev')
async def cur_reminder_call(callback: types.CallbackQuery, state: FSMContext):
    cur_state = await state.get_state()
    if cur_state != AddFood.reminder_curr:
        await callback.message.edit_text('Отменено')
        await callback.answer()
        return
    data = await state.get_data()
    await state.update_data(curr_id=data['curr_id']-(1 if data['curr_id'] > 0 else 0))
    data = await state.get_data()
    idd = data['curr_id']
    reminders = (await state.get_data())['reminders']
    kb = []
    if idd > 0 and idd < len(reminders)-1:
        kb.append(
            [
                types.InlineKeyboardButton(
                    text="<-", callback_data='curr_prev'),
                types.InlineKeyboardButton(
                    text="->", callback_data='curr_next'),
            ]
        )
    elif idd == len(reminders)-1:
        kb.append(
            [
                types.InlineKeyboardButton(
                    text="<-", callback_data='curr_prev'),
            ]
        )
    else:
        kb.append(
            [
                types.InlineKeyboardButton(
                    text="->", callback_data='curr_next'),
            ]
        )
    kb.append(
        [
            types.InlineKeyboardButton(
                text="удалить", callback_data="delete_note")
        ]
    )
    keyboard = types.InlineKeyboardMarkup(
        inline_keyboard=kb
    )
    date = datetime.strptime(
        reminders[idd]['dateNotif'], '%Y-%m-%dT%H:%M:%S.%fZ')
    sdate = datetime.strftime(date, "%d.%m.%Y %H:%M")
    await callback.message.edit_text(f"Название: {reminders[idd]['message']}\n{sdate}\nТип: {REMINDER_TYPES[reminders[idd]['restartMode']]}", reply_markup=keyboard)
    await callback.answer()


@dp.callback_query(F.data == 'curr_next')
async def cur_reminder_call(callback: types.CallbackQuery, state: FSMContext):
    cur_state = await state.get_state()
    if cur_state != AddFood.reminder_curr:
        await callback.message.edit_text('Отменено')
        await callback.answer()
        return
    reminders = (await state.get_data())['reminders']
    data = await state.get_data()
    await state.update_data(curr_id=data['curr_id']+(1 if data['curr_id'] < len(reminders)-1 else 0))
    data = await state.get_data()
    idd = data['curr_id']
    kb = []
    if idd > 0 and idd < len(reminders)-1:
        kb.append(
            [
                types.InlineKeyboardButton(
                    text="<-", callback_data='curr_prev'),
                types.InlineKeyboardButton(
                    text="->", callback_data='curr_next'),
            ]
        )
    elif idd == len(reminders)-1:
        kb.append(
            [
                types.InlineKeyboardButton(
                    text="<-", callback_data='curr_prev'),
            ]
        )
    else:
        kb.append(
            [
                types.InlineKeyboardButton(
                    text="->", callback_data='curr_next'),
            ]
        )
    kb.append(
        [
            types.InlineKeyboardButton(
                text="удалить", callback_data="delete_note")
        ]
    )
    keyboard = types.InlineKeyboardMarkup(
        inline_keyboard=kb
    )
    date = datetime.strptime(
        reminders[idd]['dateNotif'], '%Y-%m-%dT%H:%M:%S.%fZ')
    sdate = datetime.strftime(date, "%d.%m.%Y %H:%M")
    await callback.message.edit_text(f"Название: {reminders[idd]['message']}\n{sdate}\nТип: {REMINDER_TYPES[reminders[idd]['restartMode']]}", reply_markup=keyboard)
    await callback.answer()


@dp.callback_query(F.data == 'delete_note')
async def delete_reminder(callback: types.CallbackQuery, state: FSMContext):
    if (await state.get_state()) != AddFood.reminder_curr:
        await state.clear()
        await callback.message.delete()
        await callback.answer()
        return
    cur_data = await state.get_data()
    async with aiohttp.ClientSession() as session:
        await session.delete(API_URL+'/notification/delete-note/'+str(callback.from_user.id)+'/'+cur_data['reminders'][cur_data['curr_id']]['id'])
    await state.clear()
    await callback.message.edit_text('Напоминание удалено')

def get_time(s):
    match = re.search(r'Время: (\d{2}:\d{2})', s)
    return match.group(1)

async def send_reminder_every_morning():
    async with aiohttp.ClientSession() as session:
        async with session.get(API_URL+"/notification/get-list-tomorrow-notes/0") as resp:
            data: dict = await resp.json()
            for user_id, notes in data.items():
                try:
                    now_date = datetime.now().strftime('(%d.%m.%Y):\n')
                    message = [f'Ваши напоминания на сегодня {now_date}']
                    n = []
                    for note in notes:
                        date = datetime.strptime(
                            note['date'], '%Y-%m-%dT%H:%M:%S.%fZ')
                        sdate = datetime.strftime(date, "%H:%M")
                        n.append(f"{note['message']}\nВремя: {sdate}\n")
                    message += sorted(n, key=lambda x: get_time(x))
                    await bot.send_message(user_id, '\n'.join(message))
                except:
                    pass
                    
            

async def send_reminder_every_evening():
    async with aiohttp.ClientSession() as session:
        async with session.get(API_URL+"/notification/get-list-tomorrow-notes/1") as resp:
            data: dict = await resp.json()
            for user_id, notes in data.items():
                try:
                    now_date = (datetime.now()+timedelta(days=1)).strftime('(%d.%m.%Y):\n')
                    message = [f'Ваши напоминания на завтра {now_date}']
                    n = []
                    for note in notes:
                        date = datetime.strptime(
                            note['date'], '%Y-%m-%dT%H:%M:%S.%fZ')
                        sdate = datetime.strftime(date, "%H:%M")
                        n.append(f"{note['message']}\nВремя: {sdate}\n")
                    message += sorted(n, key=lambda x: get_time(x))
                    await bot.send_message(user_id, '\n'.join(message))
                except:
                    pass


async def schedule_checker():
    schedule.every().day.at(MORNING_TIME).do(send_reminder_every_morning)
    schedule.every().day.at(NIGHT_TIME).do(send_reminder_every_evening)

    while 1:
        await schedule.run_pending()
        await asyncio.sleep(1)


async def main() -> None:
    asyncio.create_task(schedule_checker())
    await dp.start_polling(bot, skip_updates=True)

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO, stream=sys.stdout)
    asyncio.run(main())
